#include "stp.h"

#include "base.h"
#include "ether.h"
#include "utils.h"
#include "types.h"
#include "packet.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <unistd.h>

#include <pthread.h>
#include <signal.h>

stp_t *stp;

// stp协议专用目的地址，不用管与端口相连的交换机mac地址是什么
const u8 eth_stp_addr[] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x01};

static bool stp_is_root_switch(stp_t *stp)
{
	return stp->designated_root == stp->switch_id;
}

// 判断端口是不是指定端口
static bool stp_port_is_designated(stp_port_t *p)
{
	return p->designated_switch == p->stp->switch_id &&
		   p->designated_port == p->port_id;
}

// 判断端口状态，根端口，指定端口，非指定端口
static const char *stp_port_state(stp_port_t *p)
{
	if (p->stp->root_port &&
		p->port_id == p->stp->root_port->port_id)
		return "ROOT";
	else if (p->designated_switch == p->stp->switch_id &&
			 p->designated_port == p->port_id)
		return "DESIGNATED";
	else
		return "ALTERNATE";
}

// 如果当前节点认为自己是根节点，那么就定时从此节点认为的指定端口发送config信息
static void stp_port_send_config(stp_port_t *p)
{
	// TODO: send config packet from this port
	// fprintf(stdout, "TODO: send config packet.\n");

	// 这里，指定端口开始发送config数据包
	// 获取交换机stp元数据
	stp_t *stp = p->stp;

	// 这里判断是不是自认为的根节点，不是根节点就直接返回，
	// 如果不是根节点，并且没有跟端口，那说明config刚更新了一般，那么就不用发config
	// 这个位置是必过的，还需要进一步探讨
	// if (!stp_is_root_switch(stp) && !stp->root_port)
	// {
	// 	return;
	// }

	// 如果自认为是根节点，就执行下面的内容

	// 构建config数据包
	stp_config_t config_packet;

	// 初始化config包
	memset(&config_packet, 0, sizeof(stp_config_t));

	// 针对大于一个字节的数据要注意网络字节顺序和本机字节顺序，这些内容是实现stp协议的关键
	// 这里面包括了当前节点认为的根节点号，当前节点到根节点的开销，当前交换机的ID，当前端口的ID
	config_packet.root_id = htonll(stp->designated_root);
	config_packet.root_path_cost = htonl(stp->root_path_cost);
	config_packet.switch_id = htonll(stp->switch_id);
	config_packet.port_id = htons(p->port_id);

	// 使用stp协议头文件中定义的常量来初始化
	config_packet.header.proto_id = htons(STP_PROTOCOL_ID);
	config_packet.header.version = STP_PROTOCOL_VERSION;
	config_packet.header.msg_type = STP_TYPE_CONFIG;
	config_packet.flags = 0;
	config_packet.msg_age = htons(0);
	config_packet.max_age = htons(STP_MAX_AGE);
	config_packet.hello_time = htons(STP_HELLO_TIME);
	config_packet.fwd_delay = htons(STP_FWD_DELAY);

	// 发送当前节点的config包，主要要封装两次，首先是LLC（链路控制）层，然后是链路层，最终是 链路头|LLC头|stp包

	// 申请一段空间来放整个链路层数据包
	char *LL_packet = malloc(ETHER_HDR_SIZE + LLC_HDR_SIZE + sizeof(config_packet));

	// 然后在这里面加入链路层包头
	ether_header_t *eth_header = (ether_header_t *)LL_packet;

	// 添加对应字段
	// 目的地址和源地址，源地址是自己的mac，目的地址是stp的统一目标地址
	memcpy(eth_header->ether_dhost, eth_stp_addr, 6);
	memcpy(eth_header->ether_shost, p->iface->mac, 6);

	// ether_type在以太网帧中表示长度，注意字节顺序转换
	eth_header->ether_type = htons(LLC_HDR_SIZE + sizeof(config_packet));

	// 这里封装LLC帧，内容在链路层包头之后
	llc_header_t *llc_hd = (llc_header_t *)(LL_packet + ETHER_HDR_SIZE);

	// 三个标志位
	llc_hd->llc_dsap = LLC_DSAP_SNAP;
	llc_hd->llc_ssap = LLC_SSAP_SNAP;
	llc_hd->llc_cntl = LLC_CNTL_SNAP;

	// 这里拷贝链路层包的数据部分
	memcpy(LL_packet + ETHER_HDR_SIZE + LLC_HDR_SIZE, &config_packet, sizeof(config_packet));

	// 发送数据
	iface_send_packet(p->iface, LL_packet, ETHER_HDR_SIZE + LLC_HDR_SIZE + sizeof(config_packet));

	// 这里将数据包内容析构
	// 在send函数里面应该会自动析构，这里不用析构LL_packet
	// free(LL_packet);
}

static void stp_send_config(stp_t *stp)
{
	int i;
	for (i = 0; i < stp->nports; i++)
	{
		stp_port_t *p = &stp->ports[i];
		if (stp_port_is_designated(p))
		{
			stp_port_send_config(p);
		}
	}
}

static void stp_handle_hello_timeout(void *arg)
{
	// log(DEBUG, "hello timer expired, now = %llx.", time_tick_now());

	stp_t *stp = arg;
	stp_send_config(stp);
	stp_start_timer(&stp->hello_timer, time_tick_now());
}

// 初始化一个端口
static void stp_port_init(stp_port_t *p)
{
	stp_t *stp = p->stp;

	p->designated_root = stp->designated_root;
	p->designated_switch = stp->switch_id;
	p->designated_port = p->port_id;
	p->designated_cost = stp->root_path_cost;
}

void *stp_timer_routine(void *arg)
{
	while (true)
	{
		long long int now = time_tick_now();

		pthread_mutex_lock(&stp->lock);

		stp_timer_run_once(now);

		pthread_mutex_unlock(&stp->lock);

		usleep(100);
	}

	return NULL;
}

// 要处理几个优先级的比较，收到config之后如果发现自己的优先级比较低，就要更新节点状态
// 收到config之后如果发现自己接收到config的端口比收到的包优先级低，就更新节点状态，节点状态的更新会导致这节点其他端口的config更新
// 然后此节点的指定端口转发修改之后的config
// 如果发现自己的端口的优先级高，那么就保持该端口为指定端口的判断，继续保持原状发送config

//端口之间优先级的比较
static bool ports_com(stp_t *stp, stp_port_t *p1, stp_port_t *p2)
{
	if (p1->designated_root < p2->designated_root)
		return true;
	else if (p1->designated_root == p2->designated_root && p1->designated_cost < p2->designated_cost)
		return true;
	else if (p1->designated_root == p2->designated_root && p1->designated_cost == p2->designated_cost && p1->designated_switch < p2->designated_switch)
		return true;
	else if (p1->designated_root == p2->designated_root && p1->designated_cost == p2->designated_cost && p1->designated_switch == p2->designated_switch && p1->designated_port < p2->designated_port)
		return true;
	else
		return false;
}

//config消息之间的优先级比较
static bool cons_cmp(stp_t *stp, stp_port_t *con1, struct stp_config *con2)
{
	if (con1->designated_root < ntohll(con2->root_id))
		return true;
	else if (con1->designated_root == ntohll(con2->root_id) && con1->designated_cost < ntohl(con2->root_path_cost))
		return true;
	else if (con1->designated_root == ntohll(con2->root_id) && con1->designated_cost == ntohl(con2->root_path_cost) && con1->designated_switch < ntohll(con2->switch_id))
		return true;
	else if (con1->designated_root == ntohll(con2->root_id) && con1->designated_cost == ntohl(con2->root_path_cost) && con1->designated_switch == ntohll(con2->switch_id) && con1->designated_port < ntohs(con2->port_id))
		return true;
	else
		return false;
}

//更新config消息
void con_update(stp_port_t *p, struct stp_config *config)
{
	p->designated_root = ntohll(config->root_id);
	p->designated_cost = ntohl(config->root_path_cost);
	p->designated_switch = ntohll(config->switch_id);
	p->designated_port = ntohs(config->port_id);
}

//从非指定节点中选择优先级最高的作为根节点
stp_port_t *select_root_port(stp_t *stp)
{
	stp_port_t *root = NULL;
	int m = 0;
	for (; m < stp->nports; m++)
	{
		if (!stp_port_is_designated(&stp->ports[m]))
		{
			int flag = 0;
			int n;
			for (n = 0; n < stp->nports; n++)
			{
				if (n == m)
					continue;
				if (!ports_com(stp, &stp->ports[m], &stp->ports[n]))
				{
					flag = 1;
					break;
				}
			}
			if (flag == 0)
			{
				root = &stp->ports[m];
				break;
			}
		}
	}
	return root;
}

// 更新指定端口
void select_designated_port(stp_t *stp)
{
	int k;
	for (k = 0; k < stp->nports; k++)
	{
		if (stp_port_is_designated(&stp->ports[k]))
		{
			stp_port_t *des = &stp->ports[k];
			des->designated_root = stp->designated_root;
			des->designated_cost = stp->root_path_cost;
		}
	}
}

static void stp_handle_config_packet(stp_t *stp, stp_port_t *p,
									 struct stp_config *config)
{
	// TODO: handle config packet here
	// fprintf(stdout, "TODO: handle config packet here.\n");

	if (cons_cmp(stp, p, config)) //如果当前config优先级比较高，则发送config消息
		stp_port_send_config(p);
	else
	{
		con_update(p, config); //将本端口的config替换为收到的config消息
		stp_port_t *root = NULL;
		root = select_root_port(stp); // 从非指定端口中选取根节点
		if (root == NULL)			  //如果不存在根节点，则该节点为根节点
		{
			stp->designated_root = stp->switch_id;
			stp->root_path_cost = 0;
		}
		else //否则，选择通过根端口连接到根节点，更新节点 状态
		{
			stp->root_port = root;
			stp->designated_root = root->designated_root;
			stp->root_path_cost = root->designated_cost + root->path_cost;
		}

		select_designated_port(stp); //更新指定端口

		stp_stop_timer(&stp->hello_timer); //停止主动发送
		stp_send_config(stp);			   //从指定端口转发更新后的config
	}
}

static void *stp_dump_state(void *arg)
{
#define get_switch_id(switch_id) (int)(switch_id & 0xFFFF)
#define get_port_id(port_id) (int)(port_id & 0xFF)

	pthread_mutex_lock(&stp->lock);

	bool is_root = stp_is_root_switch(stp);
	if (is_root)
	{
		log(INFO, "this switch is root.");
	}
	else
	{
		log(INFO, "non-root switch, designated root: %04x, root path cost: %d.",
			get_switch_id(stp->designated_root), stp->root_path_cost);
	}

	int i;
	for (i = 0; i < stp->nports; i++)
	{
		stp_port_t *p = &stp->ports[i];
		log(INFO, "port id: %02d, role: %s.", get_port_id(p->port_id),
			stp_port_state(p));
		log(INFO, "\tdesignated ->root: %04x, ->switch: %04x, "
				  "->port: %02d, ->cost: %d.",
			get_switch_id(p->designated_root),
			get_switch_id(p->designated_switch),
			get_port_id(p->designated_port),
			p->designated_cost);
	}

	pthread_mutex_unlock(&stp->lock);

	exit(0);
}

static void stp_handle_signal(int signal)
{
	if (signal == SIGTERM)
	{
		log(DEBUG, "received SIGTERM, terminate this program.");

		pthread_t pid;
		pthread_create(&pid, NULL, stp_dump_state, NULL);
	}
}

void stp_init(struct list_head *iface_list)
{
	stp = malloc(sizeof(*stp));

	// set switch ID
	u64 mac_addr = 0;
	iface_info_t *iface = list_entry(iface_list->next, iface_info_t, list);

	int i;
	for (i = 0; i < sizeof(iface->mac); i++)
	{
		mac_addr <<= 8;
		mac_addr += iface->mac[i];
	}
	stp->switch_id = mac_addr | ((u64)STP_BRIDGE_PRIORITY << 48);

	// 一开始所有节点都认为自己是根节点
	stp->designated_root = stp->switch_id;
	// 如果是根节点，那么到达跟节点的开销就是0
	stp->root_path_cost = 0;
	// 如果当前节点是根节点，那么当前节点就没有根端口
	stp->root_port = NULL;

	// 节点会定时发送配置信息，直到自己已经不是根节点了，每个节点会根据收到的配置信息，更新配置信息中的一些条目
	// 然后向其他方向转发
	stp_init_timer(&stp->hello_timer, STP_HELLO_TIME,
				   stp_handle_hello_timeout, (void *)stp);

	stp_start_timer(&stp->hello_timer, time_tick_now());

	// 这个交换机总端口数量
	stp->nports = 0;

	list_for_each_entry(iface, iface_list, list)
	{

		// 遍历所有的交换机的端口
		stp_port_t *p = &stp->ports[stp->nports];

		p->stp = stp;
		p->port_id = (STP_PORT_PRIORITY << 8) | (stp->nports + 1);
		p->port_name = strdup(iface->name);
		p->iface = iface;
		p->path_cost = 1;

		stp_port_init(p);

		// store stp port in iface for efficient access
		iface->port = p;

		stp->nports += 1;
	}

	pthread_mutex_init(&stp->lock, NULL);
	pthread_create(&stp->timer_thread, NULL, stp_timer_routine, NULL);

	signal(SIGTERM, stp_handle_signal);
}

void stp_destroy()
{
	pthread_kill(stp->timer_thread, SIGKILL);

	int i;
	for (i = 0; i < stp->nports; i++)
	{
		stp_port_t *port = &stp->ports[i];
		port->iface->port = NULL;
		free(port->port_name);
	}

	free(stp);
}

void stp_port_handle_packet(stp_port_t *p, char *packet, int pkt_len)
{
	stp_t *stp = p->stp;

	pthread_mutex_lock(&stp->lock);

	// protocol insanity check is omitted
	struct stp_header *header = (struct stp_header *)(packet + ETHER_HDR_SIZE + LLC_HDR_SIZE);

	if (header->msg_type == STP_TYPE_CONFIG)
	{
		stp_handle_config_packet(stp, p, (struct stp_config *)header);
	}
	else if (header->msg_type == STP_TYPE_TCN)
	{
		log(ERROR, "TCN packet is not supported in this lab.");
	}
	else
	{
		log(ERROR, "received invalid STP packet.");
	}

	pthread_mutex_unlock(&stp->lock);
}
